#include <stdio.h>
#include <math.h>

/*【8大函数】*/

/*【1】读取pgm为矩阵 函数*/  //读图片的
int pgm_to_matrix(const char *file_path, const int n_rows, const int n_cols, double A[n_rows][n_cols]);

/*【2】矩阵拉成一条行向量 函数*/  //展开为输入
void matrix_to_vector(const int n_rows, const int n_cols, const double matrix[n_rows][n_cols], double vector[1][n_rows*n_cols]);

/*【3】向量变矩阵 函数*/  //txt的权重变矩阵
void vector_to_matrix(const int n_rows, const int n_cols, const double vector[1][n_rows*n_cols], double matrix[n_rows][n_cols]);

/*【4】读取txt为向量 函数*/  //读权重与偏置的
void read_txt_data(const char *txt_path, const int n, double array[1][n]);

/*【5】矩阵加法 函数*/  //矩阵相加
void add_matrices(const int n_rows, const int n_cols, const double mat1[n_rows][n_cols], const double mat2[n_rows][n_cols], double result[n_rows][n_cols]);

/*【6】矩阵乘法 函数*/  //矩阵乘法
void matrix_mul(const int n_rows, const int n_cols, const int p, const double matrix1[n_rows][p], const double matrix2[p][n_cols], double matrix_product[n_rows][n_cols]);

/*【7】激活 函数*/  //激活函数
void active_function(const int SIZE, const double F[1][SIZE], double G[1][SIZE]);

/*【8】softmax 函数*/  //原始输出转为概率分布(置信度)
int soft_max(const int n_rows, const int n_cols, const double L[n_rows][n_cols], double S[n_rows][n_cols]);

int main(){
    double A[28][28] = {0};  //读取的pgm矩阵
    double B[1][784] = {0};  //展开的pgm矩阵 | A展开为B

    double W1[1][100352] = {0};  //W1向量
    double C[784][128] = {0};  //W1矩阵
    double D[1][128] = {0};  //输入向量 乘以 W1权重矩阵 | D = B x C | 即L1层输出预向量1
    double E[1][128] = {0};  //B1偏置向量
    double F[1][128] = {0};  //（权重*输入）向量1 加 B1偏置向量 | F = D + E | 即L1层输出预向量2

    double G[1][128] = {0};  //使用激活函数激活，即L1层输出最终向量，也是L2层的输入

    double W2[1][1280] = {0};  //W2向量
    double W2m[128][10] = {0};  //W2矩阵
    double H[1][10] = {0};  //输入向量 乘以 W2权重矩阵 | H = G x W2m | 即L2层输出预向量1
    double K[1][10] = {0};  //B2偏置向量
    double L[1][10] = {0};  //（权重*输入）向量2 加 B2偏置向量 | L = H + K | 即L2层输出预向量2

    double S[1][10] = {0};  //不用激活函数激活了，L2层输出的最终向量直接softmax


start:
    char filenames[10][256] = {0};  //存最多10个文件名，名称长度不超过256（包含'\0'）
    char file_path[10][512] = {0};  //储存pgm文件地址，储存数需与上面保持一致
    int file_count = 0;  //统计输入文件的数量

    printf("please input filenames:");  //请求输入文件名

    /*从输入中获取文件名称，我这里限制了最多十个，超出部分会被忽略*/
    int ifpass = 1;
    while(file_count < 10){
    scanf("%255[^,\n]", filenames[file_count]);  //读取文件名称，scanf自动补'\0'
    sprintf(file_path[file_count], "%s%s", "images/", filenames[file_count]);
    file_count++;  //计数加一
    if(getchar() != '\n'){}
    else{ifpass = 0; break;}  //读走逗号和换行符，并及时停止
    }

    /*清空缓存区*/
    int c;
    if(ifpass == 1){
        while((c = getchar()) != '\n' && c != EOF){}
    }

    /*神经网络核心*/
    for(int i = 0; i < file_count; i++){

        /*原始输入*/
        if(pgm_to_matrix(file_path[i], 28, 28, A) == 0)continue;  //判断文件是否存在，并读取pgm为A矩阵
        matrix_to_vector(28, 28, A, B);  //展开A为输入向量B


        /*夹层L1*/
        read_txt_data("input_files/W1.txt", 100352, W1);  //从txt文件读取W1向量
        vector_to_matrix(784, 128, W1, C);  //转W1为矩阵C

        matrix_mul(1, 128, 784, B, C, D);  //D = B x C  得到权重乘以输入向量

        read_txt_data("input_files/B1.txt", 128, E);  //从txt文件读取B1偏置向量
        add_matrices(1, 128, D, E, F);  //F = D + E  （权重*输入）向量1 + 偏置向量1


        /*L2输入*/
        active_function(128, F, G);  //使用激活函数激活，得到下一夹层的输入向量


        /*夹层L2*/
        read_txt_data("input_files/W2.txt", 1280, W2);  //从txt文件读取W2向量
        vector_to_matrix(128, 10, W2, W2m);  //转W2为矩阵W2m

        matrix_mul(1, 10, 128, G, W2m, H);  //H = G x W2m  得到权重乘以输入向量

        read_txt_data("input_files/B2.txt", 10, K);  //从txt文件读取B2偏置向量
        add_matrices(1, 10, H, K, L);  //L = H + K  （权重*输入）向量2 + 偏置向量2


        /*L2结果*/
        printf("%s:%d\n", filenames[i], soft_max(1, 10, L, S));  //L2层输出的最终向量直接softmax

    }

    /*是否继续*/
    char choice;
    printf("do you want to continue? please input [Y or N]:");
    scanf("%c",&choice);
    if(choice == 'Y' || choice == 'y'){while(getchar() != '\n'){}; goto start;}
    printf("Bye\n");
    return 0;
}

/*【1】读取pgm为矩阵 函数*/
int pgm_to_matrix(const char *file_path, const int n_rows, const int n_cols, double A[n_rows][n_cols]){
    //看过了，都是P5、28*28、255。就不再用代码判断了。

    FILE *file_pointer = fopen(file_path, "rb");  //打开pgm文件，只读（二进制模式）
    if (file_pointer == NULL){printf("invalid file.\n");return 0;}  //找不到文件，报错

    /*丢弃固定文件头*/
    char rubbish[256]; // 临时数组，用于读取并丢弃文件头
    fgets(rubbish, sizeof(rubbish), file_pointer);  //跳过第1行："P5"
    fgets(rubbish, sizeof(rubbish), file_pointer);  //跳过第2行："# Created by Image Processing"
    fgets(rubbish, sizeof(rubbish), file_pointer);  //跳过第3行："28 28"
    fgets(rubbish, sizeof(rubbish), file_pointer);  //跳过第4行："255"
    
    /*读取像素数据并存入A*/
    unsigned char pixel;  //这里老师文档用的是char，实际上应当使用unsigned char，不过我担心是否是用char来训练的数据。到时候需要注意。
    for(int i = 0; i < n_rows; i++){
        for(int j = 0; j < n_cols; j++){
            pixel = fgetc(file_pointer);
            A[i][j] = (double)pixel/255.0;  // 归一化
        }
    }

    fclose(file_pointer);  //关闭文件
    return 1;
}

/*【2】矩阵拉成一条行向量 函数*/
void matrix_to_vector(const int n_rows, const int n_cols, const double matrix[n_rows][n_cols], double vector[1][n_rows*n_cols]){
    for(int i = 0; i < n_rows; i++){
        for(int j = 0; j < n_cols; j++){
            vector[0][i*n_cols + j] = matrix[i][j];
        }
    }
}

/*【3】向量变矩阵 函数*/
void vector_to_matrix(const int n_rows, const int n_cols, const double vector[1][n_rows*n_cols], double matrix[n_rows][n_cols]){
    for(int i = 0; i < n_rows; i++){
        for(int j = 0; j < n_cols; j++){
            matrix[i][j] = vector[0][i*n_cols + j];
        }
    }
}

/*【4】读取txt为向量 函数*/  //读权重与偏置的
void read_txt_data(const char *txt_path, const int n, double array[1][n]){

    FILE *file_pointer = fopen(txt_path, "r");  //打开txt文件，只读
    if (file_pointer == NULL){printf("这条消息如果跳出来，说明找不到txt文件，或者代码有问题。\n");return;}

    for(int i = 0; i < n; i++){fscanf(file_pointer, "%lf", &array[0][i]);}  //储存到向量
    
    fclose(file_pointer);  //关闭文件
}

/*【5】矩阵加法 函数*/  //矩阵相加
void add_matrices(const int n_rows, const int n_cols, const double mat1[n_rows][n_cols], const double mat2[n_rows][n_cols], double result[n_rows][n_cols]){
    for(int i = 0; i < n_rows; i++){
        for(int j = 0; j < n_cols; j++){
            result[i][j] = mat1[i][j] + mat2[i][j];
        }
    }
}

/*【6】矩阵乘法 函数*/
void matrix_mul(const int n_rows, const int n_cols, const int p, const double matrix1[n_rows][p], const double matrix2[p][n_cols], double matrix_product[n_rows][n_cols]){
    for(int i = 0; i < n_rows; i++){
        for(int j = 0; j < n_cols; j++){
            double sum = 0.0;
            for(int k = 0; k < p; k++){
                sum += matrix1[i][k] * matrix2[k][j];  //点积计算
            }
            matrix_product[i][j] = sum;
        }
    }
}

/*【7】激活 函数*/  //这是非常经典的ReLU激活函数
void active_function(const int SIZE, const double F[1][SIZE], double G[1][SIZE]){
    for(int i = 0; i < SIZE; i++){
        if (F[0][i] >= 0.0){G[0][i] = F[0][i];}
        else {G[0][i] = 0.0;}
    }
}

/*【8】softmax 函数*/  //原始输出转为概率分布(置信度)
int soft_max(const int n_rows, const int n_cols, const double L[n_rows][n_cols], double S[n_rows][n_cols]){

    /*找到当前行的最大值*/
    int max_col = 0;  //最大值的位置
    double max_L = L[0][0];  //假设第一个数是最大值
    for(int i = 0; i < n_cols; i++){
        if(L[0][i] > max_L){max_L = L[0][i]; max_col = i;}
    }
        
    /*计算分母*/
    double sum = 0.0;
    for(int i = 0; i < n_cols; i++){
        sum += exp(L[0][i] - max_L);
    }

    /*计算S，即置信度向量*/
    for (int m = 0; m < n_cols; m++){
        S[0][m] = exp(L[0][m] - max_L)/sum;
    }

    return max_col;
}